{
lazarus linux/windows简单实用的三层控件QFRemoteDataSet：
转到Lazarus后发现缺少delphi熟悉的三层控件，尝试过国产商业及网上的开源三层控件，
但存在或多或少的问题，始终找不到满意的三层控件（特别是linux aarch64下），决定
开发一个简单实用的lazarus三层控件（参考网上的相关代码）。
这个三层控件功能相对简单，只适合lazarus使用，但非常实用，编写的应用软件能在
windows和国产信创操作系统(linux)及CPU运行。 
2023.01.21 QQ：315175176 秋风 
}
unit QFRemoteDataSet;

{$mode ObjFPC}{$H+}

interface

uses
  Dialogs,
  DB,
  Classes,
  SysUtils,
  Math,
  LCLType,
  LCLIntf,
  Graphics,
  rtcHttpCli,
  rtcCliModule,
  rtcDB,
  rtcFunction,
  rtcInfo,
  rtcConn;

type

  TQFRemoteConnection = class(TComponent)
  private
    FSecureKey  : string;
    FServerAddr  : string;
    FServerPort  : string;
    FHttpClient  : TRtcHttpClient;
    FRtcClientModule  : TRtcClientModule;
    FCompression  : boolean;
    procedure SetServerAddr(const Value  : string);
    procedure SetServerPort(const Value  : string);
    procedure SetCompression(const Value  : boolean);
    procedure SetSecureKey(const Value  : string);
    procedure SetDataPage(const FTableName, FKeyFields, PageSize, PageNo  : string);
    //提交数据分页SQL
  public
    constructor Create(AOwner  : TComponent); override;
    destructor Destroy; override;
    function GetTableDataRecNo(TableName  : string): int64; // 取表的记录总数
    function DateTime: TDateTime;
    function GeneratorID: string;
    function FileUpload(const filename  : string): boolean;
    function FileDownload(filename, path  : string): boolean;
    function VerifyCode(image  : TBitmap; charType  : integer = 1; vclen  : integer = 10): string;
    procedure Connect;
    procedure Disconnect;
  published
    property Compression  : boolean read FCompression write SetCompression;
    property SecureKey  : string read FSecureKey write SetSecureKey;
    property ServerAddr  : string read FServerAddr write SetServerAddr;
    property ServerPort  : string read FServerPort write SetServerPort;
  end;

  TQFRemoteTable = class(TRTCMemdataset)
  private
    FConnection  : TQFRemoteConnection;
    FTableName  : string;
    FKeyFields  : string;
    FpageSize  : integer; // 每页条数
    FPageMax  : integer; // 最大页码
    FCurPage  : integer;//当前页
    FSQL  : string;
    FSData  : integer;
    FDataPagination  : boolean;
    FAutoSaveData  : boolean;//自动保存数据
    procedure SetConnection(Value  : TQFRemoteConnection);
    procedure SetTableName(Value  : string);
    procedure SetKeyFields(Value  : string);
    procedure SetpageSize(Value  : integer);
    procedure SetPageMax(Value  : integer);
    procedure SetAutoSaveData(Value  : boolean);
    procedure SetSQL(Value  : string);
    procedure SetDataPagination(Value  : boolean);
    procedure ExecuteReadRecord; //让服务端执行SQL并读取返回数据
    procedure AutoUpdaeData(Sender  : TObject);
  public
    constructor Create(AOwner  : TComponent); override;
    destructor Destroy; override;
    procedure Open;
    procedure ApplyUpdates;
    function Locate(const AKeyFields  : string; const AKeyValues  : variant;
      AOptions  : TLocateOptions): boolean;
    function GetPageMax: int64; // 返回最大页数
    function FirstPage: int64;
    function PriorPage: int64;
    function NextPage: int64;
    function LastPage: int64;
  published
    property Connection  : TQFRemoteConnection read FConnection write FConnection;
    //SetConnection;
    property TableName  : string read FTableName write FTableName;//SetTableName;
    property KeyFields  : string read FKeyFields write FKeyFields;//SetKeyFields;
    property PageSize  : integer read FpageSize write FpageSize default 100;
    //SetpageSize Default 100;
    property CurPage  : integer read FCurPage;
    property PageNoMax  : integer read FPageMax write FPageMax default 0;
    // SetPageMax Default 0;
    property SQL  : string read FSQL write SetSQL;
    property DataPagination  : boolean read FDataPagination write FDataPagination;
    //SetDataPagination;
    property AutoSaveData  : boolean read FAutoSaveData write FAutoSaveData default True;
  protected
    procedure DoAfterScroll; override;
    procedure DoBeforeScroll; override;
    property AfterScroll;
    property BeforeScroll;
  end;

  TQFRemoteQuery = class(TRTCMemdataset)
  private
    FConnection  : TQFRemoteConnection;
    FSQL  : string;
    FTableName  : string;
    FKeyFields  : string;
    FpageSize  : integer; // 每页条数
    FPageMax  : integer; // 最大页码
    FCurPage  : integer;//当前页
    FAutoSaveData  : boolean;//自动保存数据
    procedure SetSQL(Value  : string);
    procedure SetTableName(Value  : string);
    procedure SetKeyFields(Value  : string);
    procedure GetTableName;
    procedure SetpageSize(Value  : integer);
    procedure SetPageMax(Value  : integer);
    procedure ExecuteReadRecord; //让服务端执行SQL并读取返回数据
    procedure AutoUpdaeData(Sender  : TObject);
  public
    constructor Create(AOwner  : TComponent); override;
    destructor Destroy; override;
    procedure Open;
    procedure ExecSQL;
    procedure ApplyUpdates;
    function GetPageMax: int64; // 返回最大页数
    function FirstPage: int64;
    function PriorPage: int64;
    function NextPage: int64;
    function LastPage: int64;
  published
    property Connection  : TQFRemoteConnection read FConnection write FConnection;
    property SQL  : string read FSQL write SetSQL;
    property TableName  : string read FTableName write FTableName;//SetTableName;
    property KeyFields  : string read FKeyFields write FKeyFields;//SetKeyFields;
    property PageSize  : integer read FpageSize write FpageSize default 100;
    // SetpageSize Default 100;
    property CurPage  : integer read FCurPage;
    property PageNoMax  : integer read FPageMax write FPageMax default 0;
    //SetPageMax Default 0;
    property AutoSaveData  : boolean read FAutoSaveData write FAutoSaveData default True;
  end;

  TQFRemoteStoredProc = class(TRTCMemdataset)  //存储过程
  private
    FConnection  : TQFRemoteConnection;
    FStoredProcName  : string;
    FParam  : string;
    procedure SetStoredProcName(Value  : string);
    procedure SetParam(Value  : string);
  public
    constructor Create(AOwner  : TComponent);
    destructor Destroy; override;
    procedure Open;
  published
    property Connection  : TQFRemoteConnection read FConnection write FConnection;
    property StoredProcName  : string read FStoredProcName write SetStoredProcName;
    property Param  : string read FParam write SetParam;
  end;

procedure Register;

implementation

{$R QFRemoteDataSet.res}

procedure Register;
begin
  RegisterComponents('QFRemoteDataSet',
    [TQFRemoteConnection, TQFRemoteTable, TQFRemoteQuery, TQFRemoteStoredProc]);
end;

//设置数据分页参数
procedure TQFRemoteConnection.SetDataPage(
  const FTableName, FKeyFields, PageSize, PageNo  : string);
begin
  FRtcClientModule.Prepare('SELECT');
  FRtcClientModule.Param.asText['FUN'] := 'SQLEX';
  FRtcClientModule.Param.asText['TableName'] := FTableName;
  FRtcClientModule.Param.asText['KeyFields'] := FKeyFields;
  FRtcClientModule.Param.asText['PageSize'] := PageSize;
  FRtcClientModule.Param.asText['PageNo'] := PageNo;
end;

procedure TQFRemoteConnection.SetServerPort(const Value  : string);
begin
  if Value <> FServerPort then
  begin
    FServerPort := Value;
    FHttpClient.ServerPort := FServerPort;
  end;
end;

procedure TQFRemoteConnection.SetSecureKey(const Value  : string);
begin
  if Value <> FSecureKey then
  begin
    FSecureKey := Value;
    FRtcClientModule.SecureKey := Value;
  end;
end;

procedure TQFRemoteConnection.SetCompression(const Value  : boolean);
begin
  if Value <> FCompression then
  begin
    FCompression := Value;
    if Value then
      FRtcClientModule.Compression := cfast
    else
      FRtcClientModule.Compression := cnone;
  end;
end;

procedure TQFRemoteConnection.SetServerAddr(const Value  : string);
begin
  if Value <> FServerAddr then
  begin
    FServerAddr := Value;
    FHttpClient.ServerAddr := FServerAddr;
  end;
end;

function TQFRemoteConnection.DateTime: TDateTime;
var
  Resultdata  : TRtcValue;
begin
  FRtcClientModule.Prepare('SELECT');
  FRtcClientModule.Param.asText['FUN'] := 'DateTime';
  Resultdata := FRtcClientModule.Execute(False);
  Result := Resultdata.AsDateTime;
end;

function TQFRemoteConnection.FileUpload(const filename  : string): boolean;
var
  ResultData  : TRtcValue;
  res  : TRtcRecord;
  ms  : TMemoryStream;
begin
  FRtcClientModule.Prepare('FileUpload');
  FRtcClientModule.param.asText['filename'] := ExtractFileName(filename);
  ms := TMemoryStream.Create;
  ms.LoadFromFile(filename);
  FRtcClientModule.param.asByteStream['file'] := ms;
  ms.Free;
  ResultData := FRtcClientModule.Execute(False);
  res := ResultData.asRecord;
  if res.AsBoolean['ok'] then
    Result := True
  else
    Result := False;
  ResultData.Extract;
  ResultData.Free;
end;

function TQFRemoteConnection.VerifyCode(image  : TBitmap; charType  : integer = 1;
  vclen  : integer = 10): string;
var
  ms  : TMemoryStream;
  ResultData  : TRTCValue;
  res  : TRtcRecord;
begin
  FRtcClientModule.Prepare('VerifyCode');
  if vclen < 1 then vclen := 10;
  FRtcClientModule.param.AsInteger['CharType'] := charType;
  //chartype=1：大写+数字 =2：大写 =3：数字
  FRtcClientModule.param.AsInteger['length'] := vclen;
  ResultData := FRtcClientModule.Execute(False);
  res := ResultData.asRecord;
  ms := TMemoryStream.Create;
  ms.LoadFromStream(res.asByteStream['image']);
  image.LoadFromStream(ms);
  ms.Free;
  Result := res.AsString['VerifyCode'];
  ResultData.Extract;
  ResultData.Free;
end;

function TQFRemoteConnection.FileDownload(filename, path  : string): boolean;
var
  ms  : TMemoryStream;
  ResultData  : TRTCValue;
  res  : TRtcRecord;
begin
  if (path <> '') and (path[Length(path)] <> PathDelim) then
  begin
    path := path + PathDelim;
    ForceDirectories(path);
  end;
  FRtcClientModule.Prepare('FileDownload');
  FRtcClientModule.param.asText['filename'] := filename;
  ResultData := FRtcClientModule.Execute(False);
  res := ResultData.asRecord;
  if res.AsBoolean['ok'] then
  begin
    ms := TMemoryStream.Create;
    ms.LoadFromStream(res.asByteStream['file']);
    ms.SaveToFile(path + filename);
    ms.Free;
    Result := True;
  end
  else
    Result := False;
  ResultData.Extract;
  ResultData.Free;
end;

procedure TQFRemoteConnection.Connect;
begin
  FHttpClient.Disconnect;
  FHttpClient.Connect();
end;

procedure TQFRemoteConnection.Disconnect;
begin
  FHttpClient.Disconnect;
end;

function TQFRemoteConnection.GeneratorID: string;
var
  Resultdata  : TRtcValue;
begin
  FRtcClientModule.Prepare('SELECT');
  FRtcClientModule.Param.asText['FUN'] := 'GeneratorID';
  Resultdata := FRtcClientModule.Execute(False);
  Result := Resultdata.AsString;
end;

function TQFRemoteConnection.GetTableDataRecNo(TableName  : string): int64;
var
  Resultdata  : TRtcValue;
begin
  FRtcClientModule.Prepare('SELECT');
  FRtcClientModule.Param.asText['FUN'] := 'RecordCount';
  FRtcClientModule.Param.asText['TableName'] := TableName;
  Resultdata := FRtcClientModule.Execute(False);
  Result := Resultdata.AsInteger;
  Resultdata.Free;
end;

constructor TQFRemoteConnection.Create(AOwner  : TComponent);
begin
  inherited Create(AOwner);
  FCompression := True;
  FSecureKey := '';
  FServerAddr := 'localhost';
  FServerPort := '8112';
  FHttpClient := TRtcHttpClient.Create(nil);
  FRtcClientModule := TRtcClientModule.Create(nil);

  FHttpClient.ReconnectOn.ConnectLost := True;
  FHttpClient.AutoConnect := True;
  FHttpClient.UseProxy := False;
  //设为True时，第一连接时约需要2-3秒，设为False则马上连接
  FHttpClient.ServerAddr := FServerAddr;
  FHttpClient.ServerPort := FServerPort;

  FRtcClientModule.Client := fHttpClient;
  FRtcClientModule.Compression := cFast;
  //压缩选项:cNone cFast cDefault cMax  uses添加rtcinfo
  FRtcClientModule.HyperThreading := True;
  FRtcClientModule.EncryptionKey := 16;    //加密key =0不加密 key不能是负数
  FRtcClientModule.ForceEncryption := True;
  FRtcClientModule.AutoSessions := True;
  FRtcClientModule.SecureKey := '';
  FRtcClientModule.ModuleFileName := '/QFService';
end;

destructor TQFRemoteConnection.Destroy;
begin
  FRtcClientModule.Free;
  fHttpClient.Free;
  inherited Destroy;
end;

constructor TQFRemoteTable.Create(AOwner  : TComponent);
begin
  inherited;
  FTableName := '';
  FKeyFields := '';
  self.TrackChanges := True;
  FPageSize := 100;   //每页默认记录行数
  FPageMax := 0; // 最大页码
  FCurPage := 1;//当前页
  FDataPagination := True;//默认数据分页
  FAutoSaveData := True;//设置自动保存数据
  self.OnDataChange := @AutoUpdaeData; //自动保存数据
end;

destructor TQFRemoteTable.Destroy;
begin
  inherited Destroy;
end;

procedure TQFRemoteTable.AutoUpdaeData(Sender  : TObject);
begin
  if FAutoSaveData then
    self.ApplyUpdates;
end;

procedure TQFRemoteTable.ExecuteReadRecord; //让服务端执行SQL并读取返回数据
var
  Resultdata  : TRtcValue;
begin
  Resultdata := FConnection.FRtcClientModule.Execute(False);
  Self.DisableControls;
  Self.Close;
  Self.asObject := Resultdata.asDataSet;
  inherited Open;
  Self.EnableControls;
  self.TrackChanges := True;
  Resultdata.Extract;
  Resultdata.Free;
end;

procedure TQFRemoteTable.SetConnection(Value  : TQFRemoteConnection);
begin
  if Value <> nil then
    FConnection := Value
  else
    FConnection := nil;
end;

procedure TQFRemoteTable.SetDataPagination(Value  : boolean);
begin
  if Value <> FDataPagination then
    FDataPagination := Value;
end;

procedure TQFRemoteTable.SetpageSize(Value  : integer);
begin
  if Value <> FpageSize then
  begin
    if Active then
      Close;
    FpageSize := Value;
  end;
end;

procedure TQFRemoteTable.SetAutoSaveData(Value  : boolean);
begin
  FAutoSaveData := Value;
end;

procedure TQFRemoteTable.SetPageMax(Value  : integer);
begin
  if Value <> FPageMax then
  begin
    FPageMax := Value;
  end;
end;

procedure TQFRemoteTable.SetTableName(Value  : string);
begin
  if Value <> FTableName then
  begin
    FTableName := Value;
  end;
end;

procedure TQFRemoteTable.SetKeyFields(Value  : string);
begin
  if Value <> FKeyFields then
  begin
    FKeyFields := Value;
  end;
end;

procedure TQFRemoteTable.SetSQL(Value  : string);
begin
  if Value <> FSQL then
  begin
    FSQL := Value;
  end;
end;

procedure TQFRemoteTable.DoAfterScroll;
begin
  inherited;
  //if (Self.EOF) and (FCurPage<FPageMax) and (FSData=0) then
  //begin
  //  NextPage;
  //end
  //else
  //if (Self.BOF) and (FCurPage>1) and (FSData=0) then
  //begin
  //  PriorPage;
  //end
  //else FSData:=0;
end;

procedure TQFRemoteTable.DoBeforeScroll;
begin
  inherited;
  //if (Self.EOF) and (FCurPage<FPageMax) and (FSData=0) then
  //begin
  //  NextPage;
  //end
  //else
  //if (Self.BOF) and (FCurPage>1) and (FSData=0) then
  //begin
  //  PriorPage;
  //end
  //else FSData:=0;
end;

function TQFRemoteTable.Locate(const AKeyFields  : string; const AKeyValues  : variant;
  AOptions  : TLocateOptions): boolean;
var
  Data  : string;
  keys, keyv  : TStringArray;
  i, no  : integer;
  ResultData  : TRtcValue;
begin
  keys := AKeyFields.Split([',']);
  FSQL := '';
  for i := 0 to High(keys) do
  begin
    if AOptions = [] then
      FSQL := FSQL + ' and ' + keys[i] + '=' + string(AKeyValues[i])
        .QuotedString;
    if AOptions = [loCaseInsensitive] then
      FSQL := FSQL + ' and ' + keys[i] + '=' + string(AKeyValues[i])
        .QuotedString;
    if AOptions = [loPartialKey] then
      FSQL := FSQL + ' and ' + keys[i] + ' like ' +
        QuotedStr('%' + string(AKeyValues[i]) + '%');
  end;
  FSQL := Copy(FSQL, 6, length(SQL));
  if FPageSize = 0 then
  begin
    FPageSize := 100;
  end;
  FSQL := 'SELECT * FROM ' + FTableName + ' WHERE ' + FSQL;
  FSQL := FSQL + ' ORDER BY ' + FKeyFields;
  FConnection.FRtcClientModule.Prepare('SELECT');
  FConnection.FRtcClientModule.Param.asText['FUN'] := 'Query';
  FConnection.FRtcClientModule.Param.asText['KeyFields'] := FKeyFields;
  FConnection.FRtcClientModule.Param.asText['SQL'] := FSQL;
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据;
  if self.RecordCount > 0 then
    Result := True
  else
    Result := False;
  if FPageSize <= 0 then FPageSize := 100;
  PageNoMax := ceil(FConnection.GetTableDataRecNo(FTableName) / FPageSize);
end;

procedure TQFRemoteTable.Open;
begin
  FSData := 1;
  if FPageMax = 0 then GetPageMax;
  FCurPage := 1;
  if FDataPagination then //数据分页
  begin
    //设置数据分页参数
    FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  end
  else
  begin
    FConnection.FRtcClientModule.Prepare('SELECT');
    FConnection.FRtcClientModule.Param.asText['FUN'] := 'Table';
    FConnection.FRtcClientModule.Param.asText['TableName'] := FTableName;
    FConnection.FRtcClientModule.Param.asText['KeyFields'] := FKeyFields;
  end;
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
  GetPageMax;
end;

function TQFRemoteTable.FirstPage: int64;
begin
  FSData := 1;
  if FPageMax = 0 then GetPageMax;
  FCurPage := 1;
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteTable.PriorPage: int64;
begin
  FSData := 1;
  if FPageMax = 0 then GetPageMax;
  if (FCurPage <= FPageMax) and (FCurPage > 1) then Dec(FCurPage);
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteTable.NextPage: int64;
begin
  FSData := 1;
  if FPageMax = 0 then GetPageMax;
  if (FCurPage < FPageMax) then Inc(FCurPage);
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteTable.LastPage: int64;
begin
  FSData := 1;
  if FPageMax = 0 then GetPageMax;
  FCurPage := FPageMax;
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteTable.GetPageMax: int64;
begin
  if FPageSize = 0 then
    FPageSize := 100;
  FPageMax := Ceil(FConnection.GetTableDataRecNo(FTableName) / FPageSize);
  Result := FPageMax;
end;

procedure TQFRemoteTable.ApplyUpdates;
var
  Resultdata  : TRtcValue;
begin
  if self.State in [dsinsert, dsedit] then
    self.Post;
  FConnection.FRtcClientModule.Prepare('SUBMIT');
  FConnection.FRtcClientModule.Param.asText['TableName'] := FTableName;
  FConnection.FRtcClientModule.Param.asText['KeyFields'] := FKeyFields;
  FConnection.FRtcClientModule.Param.asObject['Delta'] := self.ExtractChanges;
  Resultdata := FConnection.FRtcClientModule.Execute(False);
  Resultdata.Free;
end;


constructor TQFRemoteQuery.Create(AOwner  : TComponent);
begin
  inherited;
  FSQL := '';
  FTableName := '';
  FKeyFields := '';
  self.TrackChanges := True;
  FPageSize := 100;
  FPageMax := 0; // 最大页码
  FCurPage := 1;//当前页
  FAutoSaveData := True;//设置自动保存数据
  self.OnDataChange := @AutoUpdaeData; //自动保存数据
end;

destructor TQFRemoteQuery.Destroy;
begin
  inherited Destroy;
end;

procedure TQFRemoteQuery.AutoUpdaeData(Sender  : TObject);
begin
  if FAutoSaveData then
    self.ApplyUpdates;
end;

procedure TQFRemoteQuery.ExecuteReadRecord; //让服务端执行SQL并读取返回数据
var
  Resultdata  : TRtcValue;
begin
  Resultdata := FConnection.FRtcClientModule.Execute(False);
  Self.DisableControls;
  Self.Close;
  Self.asObject := Resultdata.asDataSet;
  inherited Open;
  Self.EnableControls;
  self.TrackChanges := True;
  Resultdata.Extract;
  Resultdata.Free;
end;

procedure TQFRemoteQuery.SetSQL(Value  : string);
begin
  if Value <> FSQL then
  begin
    FSQL := Value;
  end;
end;

procedure TQFRemoteQuery.SetpageSize(Value  : integer);
begin
  if Value <> FpageSize then
  begin
    if Active then
      Close;
    FpageSize := Value;
  end;
end;

procedure TQFRemoteQuery.SetPageMax(Value  : integer);
begin
  if Value <> FPageMax then
  begin
    FPageMax := Value;
  end;
end;

procedure TQFRemoteQuery.SetTableName(Value  : string);
begin
  if Value <> FTableName then
  begin
    FTableName := Value;
  end;
end;

procedure TQFRemoteQuery.SetKeyFields(Value  : string);
begin
  if Value <> FKeyFields then
  begin
    FKeyFields := Value;
  end;
end;

procedure TQFRemoteQuery.GetTableName;
var
  SQLText, TempSQL, SUBSQL, SUBSQL2  : string;
  WPS, SLen, i  : integer;
begin
  TempSQL := UpperCase(SQL);
  SQLText := TempSQL;
  SLen := Length(SQLText);
  i := Pos('FROM ', TempSQL);
  SUBSQL := Trim(Copy(SQLText, i + 5, SLen)).ToUpper;
  SUBSQL2 := Trim(Copy(SQL, i + 5, SLen));
  WPS := Pos('WHERE', SUBSQL);
  if WPS = 0 then
  begin
    if pos(' ', SUBSQL2) > 0 then
      SUBSQL := trim(copy(SUBSQL2, 1, pos(' ', SUBSQL2)));
    FTableName := SUBSQL2;
  end
  else
    FTableName := trim(Copy(SUBSQL2, 1, WPS - 1));
end;

procedure TQFRemoteQuery.Open;
begin
  FConnection.FRtcClientModule.Prepare('SELECT');
  FConnection.FRtcClientModule.Param.asText['FUN'] := 'Query';
  FConnection.FRtcClientModule.Param.asText['SQL'] := FSQL;
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

procedure TQFRemoteQuery.ExecSQL;
var
  Resultdata  : TRtcValue;
begin
  FConnection.FRtcClientModule.Prepare('EXECSQL');
  FConnection.FRtcClientModule.Param.asText['SQL'] := FSQL;
  Resultdata := FConnection.FRtcClientModule.Execute(False);
  Resultdata.Extract;
  Resultdata.Free;
end;

function TQFRemoteQuery.FirstPage: int64;
begin
  if FPageMax = 0 then GetPageMax;
  FCurPage := 1;
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteQuery.PriorPage: int64;
begin
  if FPageMax = 0 then GetPageMax;
  if (FCurPage <= FPageMax) and (FCurPage > 1) then Dec(FCurPage);
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteQuery.NextPage: int64;
begin
  if FPageMax = 0 then GetPageMax;
  if (FCurPage < FPageMax) then Inc(FCurPage);
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteQuery.LastPage: int64;
begin
  if FPageMax = 0 then GetPageMax;
  FCurPage := FPageMax;
  //设置数据分页参数
  FConnection.SetDataPage(FTableName, FKeyFields, FpageSize.ToString, FCurPage.ToString);
  ExecuteReadRecord; //让服务端执行SQL并读取返回数据
end;

function TQFRemoteQuery.GetPageMax: int64;
begin
  if FPageSize = 0 then
    FPageSize := 100;
  FPageMax := Ceil(FConnection.GetTableDataRecNo(FTableName) / FPageSize);
  Result := FPageMax;
end;

procedure TQFRemoteQuery.ApplyUpdates;
var
  Resultdata  : TRtcValue;
begin
  if self.Active then
  begin
    if self.State in [dsinsert, dsedit] then
      self.Post;
    GetTablename;
    FConnection.FRtcClientModule.Prepare('SUBMIT');
    FConnection.FRtcClientModule.Param.asText['TableName'] := FTableName;
    FConnection.FRtcClientModule.Param.asText['KeyFields'] := FKeyFields;
    FConnection.FRtcClientModule.Param.asObject['Delta'] := self.ExtractChanges;
    Resultdata := FConnection.FRtcClientModule.Execute(False);
    Resultdata.Free;
  end;
end;

constructor TQFRemoteStoredProc.Create(AOwner  : TComponent);
begin
  inherited;
  self.TrackChanges := True;
end;

destructor TQFRemoteStoredProc.Destroy;
begin
  inherited Destroy;
end;

procedure TQFRemoteStoredProc.SetStoredProcName(Value  : string);
begin
  FStoredProcName := Value;
end;

procedure TQFRemoteStoredProc.SetParam(Value  : string);
begin
  FParam := Value;
end;

procedure TQFRemoteStoredProc.Open;
var
  Resultdata  : TRtcValue;
begin
  FConnection.FRtcClientModule.Prepare('StoredProc');
  FConnection.FRtcClientModule.Param.asText['StoredProcName'] := FStoredProcName;
  FConnection.FRtcClientModule.Param.asText['params'] := FParam;
  Resultdata := FConnection.FRtcClientModule.Execute(False);
  Self.DisableControls;
  Self.Close;
  Self.asObject := Resultdata.asDataSet;
  inherited Open;
  Self.EnableControls;
  self.TrackChanges := True;
  Resultdata.Extract;
  Resultdata.Free;
end;

end.
